#include "d3dapplication.h"
#include "d3dUtil.h"

d3dapplication*		gD3DApplication	= 0;
IDirect3DDevice9*	gD3DDevice		= 0;

// main callback function
LRESULT CALLBACK
	MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	// don't process messages until the application has been created
	if (gD3DApplication)
		return gD3DApplication->msgProc(msg, wParam, lParam);
	else
		return DefWindowProc(hwnd, msg, wParam, lParam);
}

// initializing the application
d3dapplication::d3dapplication(HINSTANCE hInstance, std::string caption, D3DDEVTYPE devType, DWORD requestedVP)
{
	mMainWndCaption = caption;
	mDevType = devType;
	mRequestedVP = requestedVP;

	mhInst = hInstance;
	mhMainWnd = 0;
	mD3DObject = 0;
	mAppPaused = false;
	// outdated (C) way of clearing memory, but seems to work okay
	ZeroMemory(&mD3DPP, sizeof(mD3DPP));

	InitMainWindow();
	InitD3D();
}


d3dapplication::~d3dapplication(void)
{
	mD3DObject->Release();
	gD3DDevice->Release();
}

void d3dapplication::InitMainWindow()
{
	// create the window class object
	WNDCLASS wc;
	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc   = MainWndProc; 
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = mhInst;
	wc.hIcon         = LoadIcon(0, IDI_APPLICATION);
	wc.hCursor       = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = 0;
	wc.lpszClassName = "D3DWndClassName";

	if( !RegisterClass(&wc) )
	{
		MessageBox(0, "RegisterClass FAILED", 0, 0);
		PostQuitMessage(0);
	}

	// Default to a window with a client area rectangle of default size.
	RECT R = {0, 0, WINDOWED_DEFAULTWIDTH, WINDOWED_DEFAULTHEIGHT};
	AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
	mhMainWnd = CreateWindow("D3DWndClassName", mMainWndCaption.c_str(), 
		WS_OVERLAPPEDWINDOW, 100, 100, R.right, R.bottom, 
		0, 0, mhInst, 0); 

	if( !mhMainWnd )
	{
		MessageBox(0, "CreateWindow FAILED", 0, 0);
		PostQuitMessage(0);
	}

	ShowWindow(mhMainWnd, SW_SHOW);
	UpdateWindow(mhMainWnd);
}

void d3dapplication::InitD3D()
{
	// create the D3D object
	mD3DObject = Direct3DCreate9(D3D_SDK_VERSION);
	if (!mD3DObject)
	{
		MessageBox(0, "Direct3DCreate9 FAILED", 0, 0);
		PostQuitMessage(0);
	}

	// verify hardware support for the specified formats
	// for both windowed and full screen mode

	D3DDISPLAYMODE mode;
	HR(mD3DObject->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode));
	// check windowed mode
	HR(mD3DObject->CheckDeviceType(D3DADAPTER_DEFAULT, mDevType, mode.Format, mode.Format, true));
	// check fullscreen mode
	HR(mD3DObject->CheckDeviceType(D3DADAPTER_DEFAULT, mDevType, D3DFMT_X8R8G8B8, D3DFMT_X8R8G8B8, false));

	// check if requested vertex processing format is valid
	D3DCAPS9 caps;
	HR(mD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, mDevType, &caps));

	DWORD devBehaviorFlags = 0;

	if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
		// if the hardware can support it, set the behaviour flag
		// to the requested vertex processing type
		devBehaviorFlags |= mRequestedVP;
	else
		// otherwise, use software rendering
		devBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	// If pure device and HW T&L supported
	// add that info bit into the behaviour flag
	if( caps.DevCaps & D3DDEVCAPS_PUREDEVICE &&	devBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING)
		devBehaviorFlags |= D3DCREATE_PUREDEVICE;

	// fill out the information for the PresentParameters structure
	mD3DPP.BackBufferWidth            = 0; 
	mD3DPP.BackBufferHeight           = 0;
	mD3DPP.BackBufferFormat           = D3DFMT_UNKNOWN;
	mD3DPP.BackBufferCount            = 1;
	mD3DPP.MultiSampleType            = D3DMULTISAMPLE_NONE;
	mD3DPP.MultiSampleQuality         = 0;
	mD3DPP.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	mD3DPP.hDeviceWindow              = mhMainWnd;
	mD3DPP.Windowed                   = true;
	mD3DPP.EnableAutoDepthStencil     = true; 
	mD3DPP.AutoDepthStencilFormat     = D3DFMT_D24S8;
	mD3DPP.Flags                      = 0;
	mD3DPP.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	mD3DPP.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

	// finally, create our device
	HR(mD3DObject->CreateDevice(
		D3DADAPTER_DEFAULT, // primary adapter
		mDevType,           // device type
		mhMainWnd,          // window associated with device
		devBehaviorFlags,   // vertex processing
		&mD3DPP,            // present parameters
		&gD3DDevice));      // return created device
}

int d3dapplication::Run()
{
	// this replaces the main windows loop that we would typically find in the
	// WinMain() function
	// pulling it out here so we have better control of it, and can duplicate it between programs without having
	// to copy-paste a bunch of code in every time.
	MSG msg;
	// need to initialize this component to null, since we're going to check it in the while loop
	// if we didn't do this, it would complain that we were using it without initializing it
	msg.message = WM_NULL;

	__int64 cntsPerSec = 0;
	QueryPerformanceFrequency((LARGE_INTEGER*)&cntsPerSec);
	float secsPerCnt = 1.0f / (float)cntsPerSec;

	__int64 prevTimeStamp = 0;
	QueryPerformanceCounter((LARGE_INTEGER*)&prevTimeStamp);

	// this check allows the program to properly close if we e.g. click the X button
	while(msg.message != WM_QUIT)
	{
		// If there are Window messages then process them.
		if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
		{
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
		// Otherwise, do animation/game stuff.
		else
		{	
			__int64 currTimeStamp = 0;
			QueryPerformanceCounter((LARGE_INTEGER*)&currTimeStamp);
			float dt = (currTimeStamp - prevTimeStamp)*secsPerCnt;
			if (mAppPausedLastFrame)
				dt = 0.0f;

			// If the application is paused then free some CPU cycles to other 
			// applications and then continue on to the next frame.
			if( mAppPaused )
			{
				Sleep(20);
				mAppPausedLastFrame = true;
				continue;
			}
			else if(!IsDeviceLost() )
			{

				Update(dt); 
				Render();
				mAppPausedLastFrame = false;

				// Prepare for next iteration: The current time stamp becomes
				// the previous time stamp for the next iteration.
				
			}
			prevTimeStamp = currTimeStamp;
		}
	}
	return (int)msg.wParam;
}

// also replacing the Windows proc function
// handles messages that have to do with Window manipulation (minimizing, maximizing, resizing etc.)
LRESULT d3dapplication::msgProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
	// Is the application in a minimized or maximized state?
	static bool minOrMaxed = false;

	RECT clientRect = {0, 0, 0, 0};
	switch( msg )
	{

		// WM_ACTIVE is sent when the window is activated or deactivated.
		// We pause the game when the main window is deactivated and 
		// unpause it when it becomes active.
	case WM_ACTIVATE:
		if( LOWORD(wParam) == WA_INACTIVE )
			mAppPaused = true;
		else
			mAppPaused = false;
		return 0;
	
		// WM_SIZE is sent when the user resizes the window.  
	case WM_SIZE:
		if( gD3DDevice )
		{
			// change the size of the backbuffer to match
			// the new size of the window
			mD3DPP.BackBufferWidth  = LOWORD(lParam);
			mD3DPP.BackBufferHeight = HIWORD(lParam);

			if( wParam == SIZE_MINIMIZED )
			{
				mAppPaused = true;
				minOrMaxed = true;
			}
			else if( wParam == SIZE_MAXIMIZED )
			{
				mAppPaused = false;
				minOrMaxed = true;
				OnLostDevice();
				HR(gD3DDevice->Reset(&mD3DPP));
				OnResetDevice();
			}
			// Restored is any resize that is not a minimize or maximize.
			// For example, restoring the window to its default size
			// after a minimize or maximize, or from dragging the resize
			// bars.
			else if( wParam == SIZE_RESTORED )
			{
				mAppPaused = false;

				// Are we restoring from a mimimized or maximized state, 
				// and are in windowed mode?  Do not execute this code if 
				// we are restoring to full screen mode.
				if( minOrMaxed && mD3DPP.Windowed )
				{
					OnLostDevice();
					HR(gD3DDevice->Reset(&mD3DPP));
					OnResetDevice();
				}
				else
				{
					// No, which implies the user is resizing by dragging
					// the resize bars.  However, we do not reset the device
					// here because as the user continuously drags the resize
					// bars, a stream of WM_SIZE messages is sent to the window,
					// and it would be pointless (and slow) to reset for each
					// WM_SIZE message received from dragging the resize bars.
					// So instead, we reset after the user is done resizing the
					// window and releases the resize bars, which sends a
					// WM_EXITSIZEMOVE message.
				}
				minOrMaxed = false;
			}
		}
		return 0;


		// WM_EXITSIZEMOVE is sent when the user releases the resize bars.
		// Here we reset everything based on the new window dimensions.
	case WM_EXITSIZEMOVE:
		GetClientRect(mhMainWnd, &clientRect);
		// change the size of the backbuffer to match the new size of the window
		mD3DPP.BackBufferWidth  = clientRect.right;
		mD3DPP.BackBufferHeight = clientRect.bottom;
		OnLostDevice();
		HR(gD3DDevice->Reset(&mD3DPP));
		OnResetDevice();

		return 0;

		// WM_CLOSE is sent when the user presses the 'X' button in the
		// caption bar menu.
	case WM_CLOSE:
		DestroyWindow(mhMainWnd);
		return 0;

	case WM_SIZING:
		mAppPaused = true;
		return 0;

	case WM_MOVING:
		mAppPaused = true;
		return 0;

	case WM_MOVE:
		mAppPaused = false;
		return 0;

		// WM_DESTROY is sent when the window is being destroyed.
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

		/*
		case WM_KEYDOWN:
		// change screen mode when we press F or ESC
		if( wParam == VK_ESCAPE )
		EnableFullScreenMode(false);
		else if( wParam == 'F' )
		EnableFullScreenMode(true);
		return 0;
		*/
	}
	return DefWindowProc(mhMainWnd, msg, wParam, lParam);
}

void d3dapplication::EnableFullScreenMode(bool enable)
{
	// Switch to fullscreen mode.
	if( enable )
	{
		// Are we already in fullscreen mode?
		if( !mD3DPP.Windowed ) 
			return;

		int width  = GetSystemMetrics(SM_CXSCREEN);
		int height = GetSystemMetrics(SM_CYSCREEN);

		mD3DPP.BackBufferFormat = D3DFMT_X8R8G8B8;
		mD3DPP.BackBufferWidth  = width;
		mD3DPP.BackBufferHeight = height;
		mD3DPP.Windowed         = false;

		// Change the window style to a more fullscreen friendly style.
		SetWindowLongPtr(mhMainWnd, GWL_STYLE, WS_POPUP);

		// If we call SetWindowLongPtr, MSDN states that we need to call
		// SetWindowPos for the change to take effect.  In addition, we 
		// need to call this function anyway to update the window dimensions.
		SetWindowPos(mhMainWnd, HWND_TOP, 0, 0, width, height, SWP_NOZORDER | SWP_SHOWWINDOW);	
	}
	// Switch to windowed mode.
	else
	{
		// Are we already in windowed mode?
		if( mD3DPP.Windowed ) 
			return;

		RECT R = {0, 0, WINDOWED_DEFAULTWIDTH, WINDOWED_DEFAULTHEIGHT};
		AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
		mD3DPP.BackBufferFormat = D3DFMT_UNKNOWN;
		mD3DPP.BackBufferWidth  = WINDOWED_DEFAULTWIDTH;
		mD3DPP.BackBufferHeight = WINDOWED_DEFAULTHEIGHT;
		mD3DPP.Windowed         = true;

		// Change the window style to a more windowed friendly style.
		SetWindowLongPtr(mhMainWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
		 
		// If we call SetWindowLongPtr, MSDN states that we need to call
		// SetWindowPos for the change to take effect.  In addition, we 
		// need to call this function anyway to update the window dimensions.
		SetWindowPos(mhMainWnd, HWND_TOP, 100, 100, R.right, R.bottom, SWP_NOZORDER | SWP_SHOWWINDOW);
	}

	// Reset the device with the changes.
	OnLostDevice();
	HR(gD3DDevice->Reset(&mD3DPP));
	OnResetDevice();
}

bool d3dapplication::IsDeviceLost()
{
	// Get the state of the graphics device.
	HRESULT hr = gD3DDevice->TestCooperativeLevel();

	// If the device is lost and cannot be reset yet then
	// sleep for a bit and we'll try again on the next 
	// message loop cycle.
	if( hr == D3DERR_DEVICELOST )
	{
		Sleep(20);
		return true;
	}
	// Driver error, exit.
	else if( hr == D3DERR_DRIVERINTERNALERROR )
	{
		MessageBox(0, "Internal Driver Error...Exiting", 0, 0);
		PostQuitMessage(0);
		return true;
	}
	// The device is lost but we can reset and restore it.
	else if( hr == D3DERR_DEVICENOTRESET )
	{
		OnLostDevice();
		HR(gD3DDevice->Reset(&mD3DPP));
		OnResetDevice();
		return false;
	}
	else
		return false;
}